﻿using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading;

namespace K3Cloud.WebApi.Core.IoC.Cache;

/// <summary>
/// MemoryCacheService 内存缓存服务实现。
/// 基于IMemoryCache，提供高性能的内存缓存功能。
/// </summary>
[DebuggerStepThrough]
public class MemoryCacheService(IMemoryCache cache, ILogger logger) : ICacheService
{
    /// <summary>
    /// 日志记录器。
    /// </summary>
    private readonly ILogger _logger = logger;

    /// <summary>
    /// 内存缓存实例。
    /// </summary>
    private readonly IMemoryCache cache = cache;

    /// <summary>
    /// 缓存重置令牌源。
    /// </summary>
    private CancellationTokenSource _resetCacheToken = new();

    /// <summary>
    /// 默认构造函数。
    /// </summary>
    public MemoryCacheService() : this(new MemoryCache(new MemoryCacheOptions()), NullLoggerFactory.Instance.CreateLogger<MemoryCacheService>()) { }

    /// <summary>
    /// 添加缓存（默认过期时间20分钟）。
    /// </summary>
    /// <typeparam name="V">缓存值类型</typeparam>
    /// <param name="key">缓存键</param>
    /// <param name="value">缓存值</param>
    public void Add<V>(string key, V value)
    {
        Add(key, value, 1200);
    }

    /// <summary>
    /// 添加缓存（指定过期时间）。
    /// </summary>
    /// <typeparam name="V">缓存值类型</typeparam>
    /// <param name="key">缓存键</param>
    /// <param name="value">缓存值</param>
    /// <param name="cacheDurationInSeconds">缓存时长（秒）</param>
    public void Add<V>(string key, V value, int cacheDurationInSeconds)
    {
        _ = GetOrCreate<V>(key, () => value, cacheDurationInSeconds);
    }

    /// <summary>
    /// 检查缓存是否存在。
    /// </summary>
    /// <typeparam name="V">缓存值类型</typeparam>
    /// <param name="key">缓存键</param>
    /// <returns>是否存在</returns>
    public bool ContainsKey<V>(string key)
    {
        return cache.TryGetValue(key, out _);
    }

    /// <summary>
    /// 获取缓存值。
    /// </summary>
    /// <typeparam name="V">缓存值类型</typeparam>
    /// <param name="key">缓存键</param>
    /// <returns>缓存值</returns>
    public V? Get<V>(string key)
    {
        _ = cache.TryGetValue(key, out V? value);
        return value;
    }

    public IEnumerable<string> GetAllKey<V>()
    {
        const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
        object? _coherentState = cache.GetType().GetField("_coherentState", flags)?.GetValue(cache);
        if (_coherentState == null)
        {
            _logger.LogWarning("The _coherentState field is empty");
            yield break;
        }
        object? entries = _coherentState.GetType().GetField("_entries", flags)?.GetValue(_coherentState);
        if (entries is not IDictionary cacheItems)
        {
            _logger.LogWarning("The _entries field is empty");
            yield break;
        }
        foreach (DictionaryEntry cacheItem in cacheItems)
        {
            yield return cacheItem.Key.ToString()!;
        }
    }

    public V? GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue)
    {
        V result = create();
        return cacheDurationInSeconds == 0
            ? result
            : cache.GetOrCreate(cacheKey, e =>
        {
            MemoryCacheEntryOptions options = new()
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(cacheDurationInSeconds),
                SlidingExpiration = TimeSpan.FromSeconds(cacheDurationInSeconds)
            };
            _ = e.SetOptions(options);
            e.Value = result;
            _ = e.AddExpirationToken(new CancellationChangeToken(_resetCacheToken.Token));
            return result;
        });
    }

    public void Remove<V>(string key)
    {
        cache.Remove(key);
    }

    public void RemoveAll()
    {
        IEnumerable<string> keys = GetAllKey<object>();
        foreach (string key in keys)
        {
            cache.Remove(key);
        }
        _resetCacheToken.Cancel();
        _resetCacheToken.Dispose();
        _resetCacheToken = new CancellationTokenSource();
    }
}